/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "raytracer.h"

#include "local_illumination.h"
#include "common/grid.h"
#include "common/scene.h"
#include "common/camera.h"
#include "common/image.h"
#include "common/color.h"
#include "photonmapping/photon_map.h"
#include "photonmapping/global_illumination.h"
#include "object/object.h"
#include "math/ray.h"
#include "math/vector.h"
#include "material/light_material.h"
#include "material/object_material.h"
#include "material/color_material.h"
#include "material/texture_material.h"

#include <omp.h>

namespace Sirat {

namespace Ray_Tracing {

Raytracer::Raytracer(Common::Scene* _scene,Common::Camera* _camera,const unsigned short _depth) : scene(_scene), camera(_camera), image(0), photon_map(0) ,depth(_depth) , direct_illumination(true),  indirect_illumination(false), sample_rays(250U) , soft_shadow(false), shadow_rays(0), sample_photons(0), stop(false) {
	#ifdef _OPENMP
	parallel = false;
	threads = 0;
	#endif
}
	
Raytracer::Raytracer(Common::Scene* _scene,Common::Camera* _camera,const unsigned short _depth,bool _indirect_illumination = false,unsigned int _sample_rays = 250U) : scene(_scene), camera(_camera), image(0), photon_map(0) ,depth(_depth) , direct_illumination(true), indirect_illumination(_indirect_illumination), sample_rays(_sample_rays) , soft_shadow(false), shadow_rays(0), sample_photons(0) ,stop(false) 
{
	#ifdef _OPENMP
	parallel = false;
	threads = 0;
	#endif
}

Raytracer::Raytracer(void) : scene(new Common::Scene()) , camera(new Common::Camera()), image(0), photon_map(0) , depth(3U), direct_illumination(true), indirect_illumination(false) , sample_rays(250U) , soft_shadow(false), shadow_rays(250U) ,sample_photons(0) ,stop(false) { 
	#ifdef _OPENMP
	parallel = false;
	threads = 0;
	#endif
}

Raytracer::Raytracer(const Raytracer& _raytracer) {
	scene = new Common::Scene(*(_raytracer.scene));
	camera = new Common::Camera(*(_raytracer.camera));
	image = 0;
	photon_map = 0;
	
	depth = _raytracer.depth;
	direct_illumination = _raytracer.direct_illumination;
	indirect_illumination = _raytracer.indirect_illumination;
	sample_rays = _raytracer.sample_rays;
	soft_shadow = _raytracer.soft_shadow;
	shadow_rays = _raytracer.shadow_rays;
	sample_photons = _raytracer.sample_photons;
	
	#ifdef _OPENMP
	parallel = _raytracer.parallel;
	threads = _raytracer.threads;
	#endif
}

Raytracer::~Raytracer(void) { 
	if(scene) delete scene;
	if(camera) delete camera;
	if(photon_map) delete photon_map;
}

tuple<Object::Object*,Math::Vector> Raytracer::trace(const Math::Ray& _ray) const {
	return scene->nearest_object(_ray);
}

Common::Color Raytracer::calculate_color_rec(Object::Object* _object, const Math::Ray& _ray, const Math::Vector& _point,const unsigned short _depth) const {
	Common::Color color;
	if(direct_illumination) {
		if(soft_shadow) {
			color = Local_Illumination::calculate_color(_object,_ray,_point,scene,shadow_rays);
		} else color = Local_Illumination::calculate_color(_object,_ray,_point,scene);
	}
	if(photon_map != 0) color = color + photon_map->irradiance_estimate(_object,_ray,_point,sample_photons); 
	if(indirect_illumination) { 
		Common::Color indirect = Photon_Mapping::Global_Illumination::indirect(_object,_ray,_point,scene,sample_rays);
		color = (color + indirect);
	}
	Common::Color color_;
	
	if(!_depth) return color;
	
	Math::Ray ray_ = Local_Illumination::reflected_ray(_ray, _point, _object->get_normal(_point));
	
	tuple<Object::Object*,Math::Vector> intersection = trace(ray_);
	Object::Object* object_ = intersection.get<0>();
	if (object_ != 0) {	// 0 means no intersection
		Common::Color reflectiveness;
		switch(_object->get_material()->get_type()) {
			case Material::Color_Material_Type: {
				reflectiveness = static_cast<Material::Color_Material*>(_object->get_material())->get_reflectiveness();
				break;
			}
			case Material::Texture_Material_Type: {
				reflectiveness = static_cast<Material::Texture_Material*>(_object->get_material())->get_reflectiveness();
				break;
			}
		}
		Math::Vector point_ = intersection.get<1>(); 
		/**
		 * Recursive call.
		 */
		color_ = reflectiveness*calculate_color_rec(object_,ray_,point_, _depth - 1);

	}
	color = (color+color_);
	return color;
}

/**
 * Start ray tracing rendering process.
 * Here we define how threads will work.
 */
void Raytracer::render(void) const {
	
	stop = false;
	progress = 0.0;
	
	Common::Grid* grid = camera->get_grid();
	unsigned short height = grid->get_height();
	unsigned short width = grid->get_width();
	
	/* Nasty progress hack */
	double progress_inc = 1.0/((double)(height * width));
	
	/* Nasty hack */
	#ifdef _OPENMP
	bool teste = parallel;
	#endif
	Common::Image* image2 = image;
	/* End */	
	
	
	unsigned int x;
	#ifdef _OPENMP
	if(threads != 0) {
		omp_set_num_threads(threads);
	}
	#pragma omp parallel for default(shared) private(x) shared(image2) if(teste) schedule(dynamic,10)
	#endif
	for(x = 0;x < width;x++) {
		for(unsigned int y =0;y < height;y++) {
			if(stop) break; //OpenMP can't call return.
			Math::Ray ray = camera->generate_ray(x,y);

			// trace() returns the point where is a intersection
			tuple<Object::Object*,Math::Vector> intersection = trace(ray);
			Object::Object* object = intersection.get<0>();
			Common::Color color;
			if (object != 0) {	 // 0 means no intersection 
				Math::Vector point = intersection.get<1>();
				// Call recursive calculating color
				color = calculate_color_rec(object,ray,point,depth);
			}
			
			#ifdef _OPENMP
				#pragma omp critical (set_pixel)
			#endif
			{
				if(image2 != 0) image2->set_pixel(x,y,color);
				color = scene->get_background();
			}
		}
		#ifdef _OPENMP
			#pragma omp critical (inc_progress)
		#endif
		{
			progress += progress_inc*height;
		}
	}
	stop = false;
}

void Raytracer::stop_render(void) {
	stop = true;
}

void Raytracer::set_scene(Common::Scene* _scene) { scene = _scene; }

void Raytracer::set_camera(Common::Camera* _camera) { camera = _camera; }

void Raytracer::set_output(Common::Image* _image) { image = _image; }

void Raytracer::set_photon_map(Photon_Mapping::Photon_Map* _photon_map,const unsigned int _sample_photons) {
	photon_map = _photon_map;
	sample_photons = _sample_photons;
}

void Raytracer::set_depth(const unsigned short _depth) { depth = _depth; }

void Raytracer::set_direct_illumination(const bool _direct_illumination) {
	direct_illumination = _direct_illumination;
}

void Raytracer::set_indirect_illumination(const bool _indirect_illumination) {
	indirect_illumination = _indirect_illumination;
}

void Raytracer::set_sample_rays(const unsigned int _sample_rays) {
	sample_rays = _sample_rays;
}

void Raytracer::set_soft_shadow(const bool _soft_shadow) {
	soft_shadow = _soft_shadow;
}

void Raytracer::set_shadow_rays(const unsigned int _shadow_rays) {
	shadow_rays = _shadow_rays;
}

#ifdef _OPENMP
void Raytracer::set_parallel(const bool _parallel) {
	parallel = _parallel;
}

void Raytracer::set_threads(const unsigned int _threads) {
	threads = _threads;
}
#endif
	
Common::Scene* Raytracer::get_scene(void) const { return scene; }

Common::Camera* Raytracer::get_camera(void) const { return camera; }

Common::Image* Raytracer::get_output(void) const { return image; }

unsigned short Raytracer::get_depth(void) const {
	return depth; 
}

bool Raytracer::get_direct_illumination(void) const {	return direct_illumination; }

bool Raytracer::get_indirect_illumination(void) const {	return indirect_illumination; }

unsigned int Raytracer::get_sample_rays(void) const { return sample_rays; }

bool Raytracer::get_soft_shadow(void) const { return soft_shadow; }

unsigned int Raytracer::get_shadow_rays(void) const { return shadow_rays; }

double Raytracer::get_progress(void) const {
	return progress;
}

#ifdef _OPENMP
bool Raytracer::get_parallel(void) const{
	return parallel;
}

unsigned int Raytracer::get_threads(void) const { 
	return threads; 
}
#endif

Raytracer& Raytracer::operator=(const Raytracer& _raytracer) {
	if(this != &_raytracer) {
		scene = new Common::Scene(*(_raytracer.scene));
		camera = new Common::Camera(*(_raytracer.camera));
		image = 0;
		photon_map = 0;
	
		depth = _raytracer.depth;
		direct_illumination = _raytracer.direct_illumination;
		indirect_illumination = _raytracer.indirect_illumination;
		sample_rays = _raytracer.sample_rays;
		soft_shadow = _raytracer.soft_shadow;
		shadow_rays = _raytracer.shadow_rays;
		sample_photons = _raytracer.sample_photons;
	
		#ifdef _OPENMP
		parallel = _raytracer.parallel;
		threads = _raytracer.threads;
		#endif	
	}
	return *this;
}

}

}
